package com.leinao.file.service.impl;

import com.leinao.commons.Response;
import com.leinao.commons.ResponseMessage;
import com.leinao.config.EnvironmentConfig;
import com.leinao.constant.Constant;
import com.leinao.constant.FileErrorEnum;
import com.leinao.file.dao.FileMapper;
import com.leinao.file.dao.FileResourceMapper;
import com.leinao.file.dto.FilePageDto;
import com.leinao.file.dto.FileProcessDataDto;
import com.leinao.file.dto.FileUploadDto;
import com.leinao.file.dto.MultipartFileParam;
import com.leinao.file.model.BandWidth;
import com.leinao.file.model.FileResource;
import com.leinao.file.service.FileService;
import com.leinao.util.FileMD5Util;
import com.leinao.util.FileUtil;
import com.leinao.util.QiNiuUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Pattern;

@Service
public class FileServiceImpl implements FileService {

	Logger logger = LoggerFactory.getLogger(getClass());

	// 过滤文件命名的特殊字符
	private static Pattern FilePattern = Pattern.compile("[\\\\/:*?\"<>|]");

	@Autowired
	private FileMapper fileMapper;

	@Autowired
	private FileResourceMapper fileResourceMapper;

	@Autowired
	private FileUtil fileUtil;

	@Autowired
	EnvironmentConfig env;

	/**
	 * @Description 获取文件列表
	 * @param userId 用户userId
	 * @param type : 文件目录类型-com.leinao.constant.PathTypeEnum
	 * @param dir 目录
	 * @param pageNumber 当前页
	 * @param pageSize 每业条数
	 * @return
	 * @author suqiliang
	 * @Date 2018年7月2日 上午11:28:35
	 */
	@Override
	public Response<FilePageDto> getFileList(String userId, Integer type, String dir, Integer pageNumber, Integer pageSize) {
		logger.info("获取文件目录下的文件列表...");
		String baseDir = fileUtil.getBaseDir(userId, type);
		return fileUtil.getFileList(baseDir + dir, pageNumber, pageSize);
	}

	/**
	 * @Description 新建文件夹
	 * @param userId 用户userId
	 * @param type : 文件目录类型-com.leinao.constant.PathTypeEnum
	 * @param dir 目录
	 * @return
	 * @author suqiliang
	 * @Date 2018年7月2日 上午11:28:49
	 */
	@Override
	public Response<Void> addDir(String userId, Integer type, String dir) {
		logger.info("创建文件夹...");
		String baseDir = fileUtil.getBaseDir(userId, type);
		return fileUtil.addDir(baseDir + dir);
	}

	/**
	 * @Description 删除文件
	 * @param userId 用户userId
	 * @param type : 文件目录类型-com.leinao.constant.PathTypeEnum
	 * @param dirs 目录
	 * @return
	 * @author suqiliang
	 * @Date 2018年7月2日 上午11:28:58
	 */
	@Override
	public Response<Void> deleteFile(String userId, Integer type, String[] dirs) {
		logger.info("删除文件...");
		if(ArrayUtils.isEmpty(dirs)) {
			return Response.ok();
		}

		// 循环删除
		String baseDir = fileUtil.getBaseDir(userId, type);
		for(String dir : dirs) {
			Response<Void> response = fileUtil.deleteFile(baseDir + dir);
			// 存在文件夹或者文件删除失败，则立即返回
			if(ResponseMessage.SUCCESS_CODE != response.getMessage().getCode()) {
				return response;
			}
		}

		return Response.ok();
	}

	/**
	 * @Description 文件重命名
	 * @param userId 用户userId
	 * @param type : 文件目录类型-com.leinao.constant.PathTypeEnum
	 * @param dir 旧目录名称
	 * @param newDir 新目录名称
	 * @return
	 * @author suqiliang
	 * @Date 2018年7月2日 上午11:29:05
	 */
	@Override
	public Response<Void> renameFile(String userId, Integer type, String dir, String newDir) {
		logger.info("文件重命名...");
		String baseDir = fileUtil.getBaseDir(userId, type);
		return fileUtil.renameFile(baseDir + dir, baseDir + newDir);
	}

	/**
	 * @Description 文件复制
	 * @param userId 用户userId
	 * @param type : 文件目录类型-com.leinao.constant.PathTypeEnum
	 * @param dirs 旧目录名称
	 * @return
	 * @author  suqiliang
	 * @Date    2018年7月2日  上午11:31:22
	 */
	@Override
	public Response<Void> copyFile(String userId, Integer type, String[] dirs) {
		logger.info("文件复制...");
		if(ArrayUtils.isEmpty(dirs)) {
			return Response.ok();
		}

		// 循环复制
		String baseDir = fileUtil.getBaseDir(userId, type);
		for(String dir : dirs) {
			Response<Void> response = fileUtil.copyFile(baseDir + dir);
			// 存在文件夹或者文件复制失败，则立即返回
			if(ResponseMessage.SUCCESS_CODE != response.getMessage().getCode()) {
				return response;
			}
		}
		return Response.ok();
	}

	/**
	 * @Description 插入用户下载文件的带宽
	 * @param userId : 用户userId
	 * @param bandWidth : 带宽
	 * @author sofa
	 * @date 2018/10/16 下午3:49
	 */
	@Override
	public int insertUserDownBandWidth(String userId, BigDecimal bandWidth) {
		logger.info("userId: " + userId);
		logger.info("user use bandWidth: " + bandWidth);

		BandWidth bandWidthBean = new BandWidth();
		bandWidthBean.setUserId(userId);
		bandWidthBean.setBandWidth(bandWidth.divide((new BigDecimal(1024)), 4, BigDecimal.ROUND_HALF_UP));
		try {
			fileMapper.insertUserDownBandWidth(bandWidthBean);
			logger.info("insert file_center Down bandWidth success");
		} catch (Exception e) {
			logger.error("insert file_center Down bandWidth failed", e);
		}
		return 0;
	}

	/**
	 * @Description 插入用户任务下载文件的带宽
	 * @param userId : 用户userId
	 * @param bandWidth : 带宽
	 * @author sofa
	 * @date 2018/10/16 下午3:49
	 */
	@Override
	public int insertUserJobBandwidth(String userId, BigDecimal bandWidth) {
		logger.info("userId: " + userId);
		logger.info("user use bandWidth: " + bandWidth);
		BandWidth bandWidthBean = new BandWidth();
		bandWidthBean.setUserId(userId);
		bandWidthBean.setBandWidth(bandWidth.divide((new BigDecimal(1024)), 4, BigDecimal.ROUND_HALF_UP));
		try {
			fileMapper.insertUserJobBandwidth(bandWidthBean);
			logger.info("insert file_center bandWidth success");
		} catch (Exception e) {
			logger.error("insert file_center bandWidth failed");
			logger.error(e.getMessage(), e);
			return 0;
		}
		return 1;
	}

	/**
	 * @Description 上传文件或文件夹
	 * @param fileUploadDto  文件上传对象
	 * @author lizi
	 * @date 2018/11/6 下午12:39
	 */
	@Override
	public Response<Void> upload(FileUploadDto fileUploadDto) {
		// 文件处理中间数据
		FileProcessDataDto fileProcessData = new FileProcessDataDto();
		return fileUtil.uploadFile(fileUploadDto, fileProcessData);
	}

	/**
     * @Description 上传文件方法2,处理文件分块，基于MappedByteBuffer来实现文件的保存
     * @param param 大文件上传参数
     * @author sofa
     * @date 2018/10/16 下午3:49
     */
	@Override
	public Response<Void> uploadFileByMappedByteBuffer(MultipartFileParam param) {
		try {
			String originalFilename = param.getFile().getOriginalFilename();
			logger.info("originalFilename is {}", originalFilename);
			String fullPath = param.getDir() + param.getMd5();
			File tmpDir = new File(fullPath);
			File tmpFile = new File(fullPath, originalFilename);
			if (!tmpDir.exists()) {
				tmpDir.mkdirs();
			}
			@SuppressWarnings("resource")
			RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
			FileChannel fileChannel = tempRaf.getChannel();

			// 写入该分片数据
			Long offset = env.getChunkSize() * param.getChunk();
			byte[] fileData = param.getFile().getBytes();
			MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset,
					fileData.length);
			mappedByteBuffer.put(fileData);
			// 释放
			FileMD5Util.freedMappedByteBuffer(mappedByteBuffer);
			fileChannel.close();

			boolean isOk = checkAndSetUploadProgress(param, fullPath);
			if (isOk) {
				Response.ok(200, 0, "上传成功", param.getFile().getOriginalFilename());
			}
		} catch (FileNotFoundException e) {
			logger.error("文件未找到");
			logger.error(e.getMessage(), e);
			return Response.error(200, FileErrorEnum.FILE_NOT_EXIST.getSeq(),
					FileErrorEnum.FILE_NOT_EXIST.getDesc(), null);
		} catch (IOException e) {
			logger.error("上传出现问题了");
			logger.error(e.getMessage(), e);
			return Response.error(200, FileErrorEnum.FILE_UPLOAD_ERROR.getSeq(),
					FileErrorEnum.FILE_UPLOAD_ERROR.getDesc(), null);
		}
		return Response.error(200, FileErrorEnum.FILE_UPLOAD_ERROR.getSeq(),
				FileErrorEnum.FILE_UPLOAD_ERROR.getDesc(), null);
	}
	
	/**
     * 检查并修改文件上传进度
     *
     * @param param
     * @param uploadDirPath
     * @return
	 * @throws FileNotFoundException 
     * @throws IOException
     */
    @SuppressWarnings("unused")
	private boolean checkAndSetUploadProgress(MultipartFileParam param, String uploadDirPath) throws FileNotFoundException,IOException {
			String fileName = param.getFile().getOriginalFilename();
			File confFile = new File(uploadDirPath, fileName + ".conf");
			RandomAccessFile accessConfFile = new RandomAccessFile(confFile, "rw");
			//把该分段标记为 true 表示完成
			logger.info("set part " + param.getChunk() + " complete");
			accessConfFile.setLength(param.getChunks());
			accessConfFile.seek(param.getChunk());
			accessConfFile.write(Byte.MAX_VALUE);

			//completeList 检查是否全部完成,如果数组里是否全部都是(全部分片都成功上传)
			byte[] completeList = FileUtils.readFileToByteArray(confFile);
			byte isComplete = Byte.MAX_VALUE;
			for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
			    //与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE
			    isComplete = (byte) (isComplete & completeList[i]);
			    logger.info("check part " + i + " complete?:" + completeList[i]);
			}

			accessConfFile.close();
			return false;
    }

	/**
	 * @Description 文件上传七牛云
	 * @param file : 文件流
	 * @author lizi
	 * @date 2018/11/16 下午4:03
	 */
	@Override
	public Response<FileResource> qiniuFileUpload(MultipartFile file) {
		// 生成文件七牛云路径
		String path = FileMD5Util.uuid();
		QiNiuUtil qiNiuUtil = QiNiuUtil.getInstance();
		String fileName = file.getOriginalFilename();

		// 获取文件后缀
		String postfix = "";
		if(!StringUtils.isEmpty(fileName) && fileName.lastIndexOf(Constant.CUT_PERIOD) > 0){
			postfix = fileName.substring(fileName.lastIndexOf(Constant.CUT_PERIOD), fileName.length());
		}
		path += postfix;

		// 上传文件至七牛云
		int result = 0;
		try {
			result = qiNiuUtil.uploadFile(file.getInputStream(), path);
		} catch (IOException e) {
			logger.error("获取文件流失败", e);
		}
		if(result != 1) {
			return Response.error(200, FileErrorEnum.FILE_UPLOAD_ERROR.getSeq(), FileErrorEnum.FILE_UPLOAD_ERROR.getDesc(), null);
		}

		// 生成文件存储的Model
		FileResource fileResource  = new FileResource();
		fileResource.setName(fileName);
		fileResource.setPath(Constant.QINIU_DOMAIN + path);
		fileResource.setType(1);
		Long now = System.currentTimeMillis();
		fileResource.setCreateTime(now);
		fileResource.setUpdateTime(now);
		fileResource.setUrl(Constant.QINIU_DOMAIN + path);
		fileResourceMapper.insert(fileResource);

		return Response.ok(fileResource);
	}

	/**
	 * @Description 文件下载 支持批量下载
	 * @param userId : 用户ID
	 * @param type : 文件目录类型-com.leinao.constant.PathTypeEnum
	 * @param dirs : 下载路径
	 * @param response response对象
	 * @author lizi
	 * @date 2018/11/19 下午9:07
	 */
	@Override
	public void downLoad(String userId, Integer type, String[] dirs, HttpServletResponse response) {
		try {
			response.setContentType("application/json; charset=utf-8");
			response.setContentType("application/octet-stream");
			OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());

			// 文件处理中间数据，如带宽
			FileProcessDataDto fileProcessData = new FileProcessDataDto();
			String baseDir = fileUtil.getBaseDir(userId, type);
			// 单个文件下载
			if(dirs.length == 1 && !(Files.isDirectory(Paths.get(baseDir + dirs[0])))) {
				// 文件名需要编码，否则文件名叫 "新建文本文档.txt"，其实是含有文字 "本" 就会下载失败，很神奇吧
				String downloadFileName = java.net.URLEncoder.encode(Paths.get(dirs[0]).getFileName().toString(), Constant.DEFAULT_CHARSET);
				// 通知浏览器以下载的方式打开
				response.addHeader("Content-Disposition", "attachment;filename=" + downloadFileName);
				fileUtil.downloadFile(baseDir + dirs[0], outputStream, fileProcessData);
			} else {
				// 批量文件下载, 生成zip名称
				String downloadFileName = System.currentTimeMillis() +".zip";
				// 通知浏览器以下载的方式打开
				response.addHeader("Content-Disposition", "attachment;filename=" + downloadFileName);
				fileUtil.batchDownloadFile(dirs, baseDir, outputStream, fileProcessData);
			}

			outputStream.flush();
			outputStream.close();
		} catch (UnsupportedEncodingException e) {
			logger.error("downLoad UnsupportedEncodingException", e);
		} catch (IOException e) {
			logger.error("downLoad IOException", e);
		}
	}

	/***
	 * @Description 文件预览
	 * @param userId : 用户ID
	 * @param type : 文件目录类型-com.leinao.constant.PathTypeEnum
	 * @param filePath : 文件路径
	 * @param response response对象
	 * @author lizi
	 * @date 2018/11/29 下午3:57
	 */
	@Override
	public void previewFile(String userId, Integer type, String filePath, HttpServletResponse response) {
		// 获取用户主目录
		String baseDir = fileUtil.getBaseDir(userId, type);

		// 文件预览
		previewFile(baseDir + filePath, response);
	}

	/**
	 * @Description 文件预览
	 * @param path : 文件完整路径
	 * @author lizi
	 * @date 2018/12/18 下午6:43
	 */
	@Override
	public void previewFile(String path, HttpServletResponse response) {
		try {
			response.setContentType("application/json; charset=utf-8");
			response.setContentType("application/octet-stream");
			OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());

			// 文件处理中间数据，如带宽
			FileProcessDataDto fileProcessData = new FileProcessDataDto();

			// 预览文件readme文件不区分大小写处理
			if(path.endsWith(Constant.SMALL_README) && Files.notExists(Paths.get(path))) {
				path = path.replace(Constant.SMALL_README, Constant.BIG_README);
			} else if(path.endsWith(Constant.BIG_README) && Files.notExists(Paths.get(path))) {
				path = path.replace(Constant.BIG_README, Constant.SMALL_README);
			}

			// 判断路径
			Path previewFilePath = Paths.get(path);
			if (Files.notExists(previewFilePath)) {
				logger.error("previewFile not exists, path : {}", path);
				return;
			}

			// 下载文件流
			fileUtil.downloadFile(path, outputStream, fileProcessData);
			outputStream.flush();
			outputStream.close();
		} catch (UnsupportedEncodingException e) {
			logger.error("previewFile UnsupportedEncodingException", e);
		} catch (IOException e) {
			logger.error("previewFile IOException", e);
		}
	}

}
